using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Timers;
using System.Threading;

namespace Decav.Gatsb.Application
{
    /// <summary>
    /// Listens for incoming messages on a Microsoft SMTP server, parses them
    /// for 
    /// </summary>
    public class MessageListener : IDisposable
    {
        /// <summary>
        /// Initializes a new <see cref="MessageListener"/> on a
        /// specific path.
        /// </summary>
        /// <param name="dropBoxPath">The drop box that the message listener
        /// will check for messages.</param>
        public MessageListener(string dropBoxPath)
        {
            if (dropBoxPath == null)
                throw new ArgumentNullException("dropBoxPath");

            _dropBoxPath = dropBoxPath;
        }

        private string _dropBoxPath;
        private FileSystemWatcher _watcher;
        private Thread _parsingThread;
        private ManualResetEvent _waitHandle;
        private volatile bool _running;
        private readonly object _syncRoot = new object();
        

        /// <summary>
        /// Gets the path to the drop box where new messages are received.
        /// </summary>
        public string DropBoxPath
        {
            get
            {
                return _dropBoxPath;
            }
        }

        /// <summary>
        /// Gets whether or not this <see cref="MessageListener"/> is currently
        /// watching the directory.
        /// </summary>
        public bool Running
        {
            get
            {
                return _running;
            }
        }


        /// <summary>
        /// Begins watching the folder.
        /// </summary>
        public void Start()
        {
            lock (_syncRoot)
            {
                if (_running)
                    throw new InvalidOperationException("The MessageListener is already running.");

                _watcher = new FileSystemWatcher(_dropBoxPath);
                _watcher.Changed += new FileSystemEventHandler(FileWatcher_Changed);
                _watcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite;

                _waitHandle = new ManualResetEvent(false);

                _parsingThread = new Thread(Thread_Parse);
                _parsingThread.Name = "Mobile Message Parser";
                _parsingThread.IsBackground = true;

                _running = true;
                _parsingThread.Start();
            }
        }

        /// <summary>
        /// Ends watching the folder.
        /// </summary>
        public void Stop()
        {
            lock (_syncRoot)
            {
                if (!_running)
                    return;

                _running = false;

                _waitHandle.Set();

                _watcher.Changed -= new FileSystemEventHandler(FileWatcher_Changed);
                _watcher.Dispose();

                _parsingThread.Join();
            }
        }

        private void Thread_Parse()
        {
            while (_running)
            {
                NotifyNewMessages();

                // Wait for an event to trigger the wait handle
                if (Directory.GetFiles(_dropBoxPath).Length == 0) // when we have nothing left, then continue waiting...
                    _waitHandle.Reset();

                // Periodically short circuit the wait because sometimes
                // FileSystemWatcher fails to notify of new events.
                _waitHandle.WaitOne(TimeSpan.FromSeconds(1), false);
            }
        }

        private void NotifyNewMessages()
        {
            foreach(string file in Directory.GetFiles(_dropBoxPath))
            {
                // check that we're done writing.
                try
                {
                    using (File.Open(file, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) ;
                }
                catch
                { 
                    // If this file is in use, just skip to the next.
                    continue;
                }

                // Allow event listeners to parse the message.
                OnMessageReceived(new MessageReceivedEventArgs(file));
            }
        }

        void FileWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            _waitHandle.Set();
        }


        /// <summary>
        /// Event that is raised when a new file is added to the DropBoxPath.
        /// </summary>
        public event EventHandler<MessageReceivedEventArgs> MessageReceived;

        /// <summary>
        /// Raises the MessageReceived event to event subscribers.
        /// </summary>
        /// <remarks>
        /// Note to inheritors: You must call the base method in order to ensure that the event is raised and clean up
        /// work is properly performed.
        /// </remarks>
        /// <param name="e">Event arguments.</param>
        protected virtual void OnMessageReceived(MessageReceivedEventArgs e)
        {

            EventHandler<MessageReceivedEventArgs> handlers = MessageReceived;

            // Invoke the event handlers
            if (handlers != null)
            {
                try
                {
                    MessageReceived(this, e);
                }   // try
                catch (Exception)
                {
                    // Respond to exceptions in the handler
                    // or ignore them
                }   // catch ( Exception )
            }   // if

        }
        

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
        }

        ~MessageListener()
        {
            Dispose(false);
        }

        #endregion
    }
}
